package com.ar4j;

import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.Collection;
import java.util.Map;
import java.util.Set;

import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.ar4j.pagination.PaginationAndSort;
import com.ar4j.type.ITypeConverter;

/**
 * Main active record interface, should be implemented by all abstract domain class prototypes.
 * @param <T> the type of the record
 */
public interface ActiveRecord<T> extends Serializable {

  /**
   * @return the name of the factory in which this active record was created
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  String getFactoryName();
  
  /**
   * @return the base class of the active record (base domain class)
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  Class<T> getBaseClass();
  
  /**
   * @return the names of all the properties of this active record (persistent properties only)
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  Set<String> getPropertyNames();
  
  /**
   * @return the type of a property with the given name
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  Class<?> getPropertyType(String name);
  
  /**
   * @return true if a property with the given name is contained within this active record, false otherwise
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  boolean hasProperty(String name);
  
  /**
   * @return the value of the property with the given name as an Object
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  Object getProperty(String name);
  
  /**
   * @return the value of the property with the given name, coerced to the required type
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  <E> E getProperty(String name, Class<E> requiredType);
  
  /**
   * Sets the property of the given name to the given value
   * @return the active record that was operated on (for fluent behavior)
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  T setProperty(String name, Object value);
  
  /**
   * @return the current state of the domain object as a map of property names to their values (which can be null),
   * will include only readable properties
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  Map<String, Object> getPropertyMap();
  
  /**
   * Used during serialization to make sure we don't send the active record over the wire, only
   * its representation.
   * 
   * @return a ActiveRecordJsonWrapper to be serialized in place of the active record
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  Object writeReplace() throws ObjectStreamException;
  
  /**
   * @return a json wrapper for the current record
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  ActiveRecordJsonWrapper wrapInJson();
  
  /**
   * @see java.lang.Object#equals(Object) 
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  boolean equals(Object other);
  
  /**
   * Similar to the equals implementation except that this comparison ignores the active
   * record's ID field.
   * 
   * @see java.lang.Object#equals(Object)
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  boolean isSameContent(Object other);
  
  /**
   * @see java.lang.Object#toString() 
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  String toString();
  
  /**
   * @see java.lang.Object#hashCode() 
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  int hashCode();
  
  /**
   * @return a cloned version of the current record, without its ID set.
   * This is similar to calling getActiveRecord(ActiveRecord) on an active 
   * record factory.
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  T cloneRecord();
  
  /**
   * @return the name of the property holding the active record identifier
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  String getIdentifierPropertyName();
  
  /**
   * @return the type of the property holding the active record identifier
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  Class<?> getIdentifierPropertyType();
  
  /**
   * @return the identifier of this active record
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  Object getIdentifier();
  
  /**
   * @return if the identifier of the record is not present or represents
   * a newly created record, false otherwise 
   */
  boolean isNewRecord();
  
  /**
   * @return the name of the table backing the active record
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  String getTableName();
  
  /**
   * @return the type converter for the given property, or null if none is found
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  ITypeConverter<?, ?> getPropertyTypeConverter(String name);
  
  /**
   * @return the context within with the active record will perform its operations.
   * This context is modifiable and is a perfect way to set the active record's
   * datasource and other context properties.
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  ActiveRecordContext getContext();
  
  /**
   * Finds all records in the backing store matching the given parameters.
   * If no parameters are provided, all records of the current type are retrieved.
   */
  Collection<T> find(Map<String, ?> parameters, PaginationAndSort pagination, boolean lockForUpdate);
  
  /**
   * Similar to find(Map, PaginationAndSort, Boolean), without a lock for update.
   */
  Collection<T> find(Map<String, ?> parameters, PaginationAndSort pagination);
  
  /**
   * Similar to find(Map, PaginationAndSort), without pagination and sorting
   */
  Collection<T> find(Map<String, ?> parameters);
  
  /**
   * Finds all record with the given identifiers.
   * @throws IncorrectResultSizeDataAccessException if the number of records retrieved
   * does not match the number of given identifier values
   */
  Collection<T> findAllByIdentifiers(Collection<?> ids);
  
  /**
   * Finds a record by its identifier value, optionally locking it for update
   */
  T findByIdentifier(Object id, boolean lockForUpdate);
  
  /**
   * Finds a record by its identifier value
   */
  T findByIdentifier(Object id);
  
  /**
   * @return the number of records found in the backing store with the given parameters,
   * if no parameters are provided the number of all records of this type is returned
   */
  long count(Map<String, ?> parameters);
  
  /**
   * Reloads the current record from the backing store.
   * 
   * NOTE: will throw an exception if there is no data source set in the context
   * or if there is no identifier value for the current record
   */
  T reload();
  
  /**
   * Persists the current state of the active record and reloads from the backing store.
   */
  T save();
  
  /**
   * @return all record of the current type. Synonymous to executing a find without parameters.
   */
  Collection<T> findAll(PaginationAndSort pagination);
  
  /**
   * @return all record of the current type. Synonymous to executing a find without parameters and without pagination.
   */
  Collection<T> findAll();
  
  /**
   * @return the number of records of the current type. Synonymous to executing a count with no parameters.
   */
  long countAll();
  
  /**
   * @return true if any records are found with the given parameters, false otherwise.
   * A synonym for a count operation with the same parameters and a comparison to a zero result.
   */
  boolean exists(Map<String, ?> parameters);
  
  /**
   * @return true if a record exists for every given ID, false otherwise
   * A synonym for a count operation with the identifier property name, and the given IDs as a value.
   */
  boolean existsByIdentifier(Collection<?> ids);
  
  /**
   * @return true if a record exists with the given ID, false otherwise. 
   * A synonym for a count operation with the identifier property name, and the given ID as a value.
   */
  boolean existsByIdentifier(Object id);
  
  /**
   * Sets the readOnly flag for this active record. A readonly active record cannot persist
   * to the backing store.
   * @return the active record that was operated on (for fluent behavior)
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  T setReadOnly(boolean readOnly);
  
  /**
   * @return true if the record has been marked as read only, false otherwise
   */
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  boolean isReadOnly();
  
  /**
   * Removes the record from the backing store and clears its identifier
   */
  boolean delete();
  
  /**
   * Deletes any records that are found with the given parameters.
   * @return the number of records removed from the backing store 
   */
  int delete(Map<String, ?> parameters);
  
  /**
   * Removes records with the given identifiers.
   * @return the number of records removed from the backing store
   */
  int deleteByIdentifier(Collection<?> ids);
  
  /**
   * Removes a record with the given identifier.
   * @return true if the record was found, false otherwise
   */
  boolean deleteByIdentifier(Object id);
  
}
